/*-
 * Copyright (c) 2006 Vishal Patil <bsd.devil@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <strings.h>
#include <string.h>
#include "iscsi_util.h"
#include "iscsi_pdu_list.h"

#define PDU_BOUNDARY 4

/*
 *	Adds key val to the data segment of iSCSI PDU
 */
int 
add_key_value(struct iscsi_pdu_incore * pdu, const char * key, 
			const char * value ) {
	int key_len 	= strlen(key);
	int value_len	= strlen(value);
	char * text;

	/* 
	 * The total length is the sum of key val length + 2 characters
	 * 1 for '=' and 1 for the 0x00 terminator
	 */
	int length	= key_len + value_len + 2; 	
	int dlength	= ntoh24(pdu->pdu_hdr.bhs.dlength);

	/* Make sure we have enough room for the data */
	if (length + dlength > pdu->dlength_max ) {
		return -1;
	}
	
	/* Move pointer to an appropriate area*/
	text = (char *)pdu->data_seg + dlength;

	/* Copy the key */
	strncpy(text,key,key_len);
	text += key_len;

	/* Copy the separator */
	*text++ = ISCSI_KEY_VALUE_SEPARATOR;

	/* Copy the value */
	strncpy(text,value,value_len);
	text+= value_len;

	/* NULL terminator*/
	*text++ = '\0';		
	hton24(pdu->pdu_hdr.bhs.dlength,(dlength + length));

	return 0;
}

/*
 *	Try searching for a key in the given data segment. If found 	
 *	return the value along with the length of the value. 
 */
int
get_key_value(const char * option_key, struct iscsi_pdu_incore * pdu, 
			char * option_value) {

	uint8_t * data_seg = pdu->data_seg;
	uint32_t dlength = ntoh24(pdu->pdu_hdr.bhs.dlength);
		
	if (!data_seg) {
		return 0;
	}
	
	uint8_t key_found = 0;
	uint8_t * key = NULL,key_length;
	uint8_t * value = NULL,value_length;
	uint8_t * text	= (uint8_t *)data_seg;
	uint8_t * end 	= (uint8_t *)data_seg + dlength;

	while (text < end) {
		
		/* Strip off any NULL characters */	
		while (*text == ISCSI_TEXT_NULL) {
			text++;
		}

		/* Store the start of the key */
		key = text;
		key_length = 0;
		
		/*Start reading the key*/
		while (*text != ISCSI_KEY_VALUE_SEPARATOR) {
			key_length++;
			text++;	
		}

		if (strncmp(option_key,(const char *)key,
					key_length) == 0) {
			key_found = 1;
		}
		
		/* Skip the separator */
		text++;

		/* Store the start of value */
		value = text;
		value_length = 0;
		
		/* Start reading the key */
		while (*text != ISCSI_TEXT_NULL) {
			value_length++;
			text++;
		}

		/* If key found return */
		if (key_found) {
			strncpy(option_value,(char *)value,value_length);
			/* NULL terminator for the string */
			option_value[value_length] = '\0';
			return key_found;
		}
	}		
	return key_found;	
}

/*
 *	Generate an incore PDU from the received buffer. The incore PDU is
 *	obtained from the pdu pool
 */
struct iscsi_pdu_incore * 
buffer_to_pdu(uint8_t * buffer, uint32_t buffer_size) {
	struct iscsi_pdu_incore * pdu;	
	union iscsi_pdu_hdr * pdu_hdr = NULL;

	/* Get the header */
	pdu_hdr = (union iscsi_pdu_hdr *)buffer;
	
	uint8_t header_size = sizeof(union iscsi_pdu_hdr);
	uint8_t hlength = pdu_hdr->bhs.hlength;	
	uint32_t dlength = ntoh24(pdu_hdr->bhs.dlength); 	
	
	/* Get a required PDU */
	pdu = get_pdu(hlength,dlength);

	if (pdu == NULL)
		return NULL;

	/* Copy the iSCSI header */
	memcpy(&pdu->pdu_hdr,buffer,header_size);

	/* Copy the AHS */
	memcpy(pdu->ahs,buffer + header_size,hlength);

	/* Copy the DS */
	memcpy(pdu->data_seg,buffer + header_size + hlength,dlength);

	return pdu;
}

/*
 *	Copy the incore pdu to a data buffer and retun the PDU back to
 *	the PDU pool. Returns the size of incore PDU
 */
int
pdu_to_buffer(struct iscsi_pdu_incore * pdu, uint8_t * buffer,
		int buffer_size) {
	
	if (!pdu) {
		return -1;
	}

	int hlength = pdu->pdu_hdr.bhs.hlength;
	int ohlength = hlength;
	
	int dlength = ntoh24(pdu->pdu_hdr.bhs.dlength);
	int odlength = dlength;

	int header_size = sizeof(pdu->pdu_hdr.bhs);

	/* Ensure 4 byte boundaries */
	if ((hlength % PDU_BOUNDARY)) {
		hlength = hlength + PDU_BOUNDARY - 
			(hlength % PDU_BOUNDARY );
		pdu->pdu_hdr.bhs.hlength = hlength;
	}
	
	/* Ensure 4 byte boundaries */
	if ((dlength % PDU_BOUNDARY)) {
		dlength = dlength + PDU_BOUNDARY - 
				(dlength % PDU_BOUNDARY);
		hton24(pdu->pdu_hdr.bhs.dlength,dlength);
	}	
	
	int incore_size =  header_size + hlength + dlength;

	if (incore_size > buffer_size) {
		return -1;
	}
	
	/* Zero the buffer */
	bzero(buffer,buffer_size);
	
	/* Copy the header */
	memcpy(buffer,&pdu->pdu_hdr.bhs,header_size);
	
	/* Copy the AHS */	
	if (ohlength) {
		memcpy(buffer + header_size,pdu->ahs,ohlength);
	}
	
	/* Copy the DS */	
	if (odlength) {
		memcpy(buffer + header_size + hlength,pdu->data_seg,
				odlength);
	}

	/* Return the PSU back to the PDU pool*/
	put_pdu(pdu);

	return incore_size;
}

